﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading;
using ProjectEuler.Extensions;
using ProjectEuler.Problems;

namespace ProjectEuler.UI
{
    internal sealed class ProblemInfo : INotifyPropertyChanged
    {
        private readonly Type _type;
        private readonly ProblemInfoCollection _owner;

        private readonly EulerProblemAttribute _problem;
        private readonly DescriptionAttribute _desc;

        private Object _answer;
        private Exception _error;
        private TimeSpan? _elapsed;

        private Boolean _skip;
        private ProblemStatus _status;

        private PropertyChangedEventHandler _propertyChanged;

        internal ProblemInfo(ProblemInfoCollection owner, Type t, EulerProblemAttribute attr)
        {
            _desc = t.GetCustomAttribute<DescriptionAttribute>(false);
            _skip = attr.Skip;

            _owner = owner;
            _type = t;
            _problem = attr;
        }

        public Int32 ID
        {
            get { return _problem.ID; }
        }

        public Object Answer
        {
            get { return _answer; }
            private set
            {
                if (_answer != value)
                {
                    _answer = value;
                    OnPropertyChanged("Answer");
                    OnPropertyChanged("Result");
                }
            }
        }

        public Object CorrectAnswer
        {
            get { return _problem.Answer; }
        }

        public ProblemStatus CurrentStatus
        {
            get { return _status; }
            private set
            {
                if (_status != value)
                {
                    _status = value;
                    OnPropertyChanged("CurrentStatus");
                    OnPropertyChanged("Result");
                }
            }
        }

        public TimeSpan? Elapsed
        {
            get { return _elapsed; }
            private set
            {
                if (_elapsed != value)
                {
                    _elapsed = value;
                    OnPropertyChanged("Elapsed");
                }
            }
        }

        public Exception FailureReason
        {
            get { return _error; }
            private set
            {
                if (_error != value)
                {
                    _error = value;
                    OnPropertyChanged("FailureReason");
                    OnPropertyChanged("Result");
                }
            }
        }

        public Object Result
        {
            get 
            {
                if (FailureReason != null)
                {
                    return FailureReason.Message;
                }

                if (CurrentStatus == ProblemStatus.Executing)
                {
                    return Resources.Working;
                }

                return Answer; 
            }
        }

        public Boolean IsSelected
        {
            get { return !_skip; }
            set
            {
                if (_skip == value)
                {
                    _skip = !value;
                    OnPropertyChanged("IsSelected");

                    _owner.OnShouldRunAllChanged();
                }
            }
        }

        public String Title
        {
            get
            {
                if ((_desc != null) && !String.IsNullOrEmpty(_desc.Description))
                {
                    return ID.ToString() + ". " + _desc.Description;
                }

                return "Problem " + ID.ToString();
            }
        }

        public Boolean HasDescription
        {
            get { return (_desc != null) && !String.IsNullOrEmpty(_desc.Description); }
        }

        public void Execute()
        {
            Exception error;

            var solver = CreateSolver();
            var elapsed = TimeSpan.Zero;

            FailureReason = null;
            CurrentStatus = ProblemStatus.Executing;

            Answer = solver.Measure(out elapsed, out error);
            Elapsed = elapsed;
            FailureReason = error;

            CurrentStatus = GetProblemStatus();
        }

        public void ResetResult()
        {
            Answer = null;
            Elapsed = null;
            FailureReason = null;
            CurrentStatus = ProblemStatus.None;
        }

        private ProblemStatus GetProblemStatus()
        {
            if (FailureReason != null)
            {
                return ProblemStatus.Failed;
            }

            if (CorrectAnswer == null)
            {
                return ProblemStatus.Inconclusive;
            }

            if (CorrectAnswer.Equals(Answer))
            {
                return ProblemStatus.Correct;
            }

            return ProblemStatus.Wrong;
        }

        private Func<Object> CreateSolver()
        {
            return ((EulerProblem)Activator.CreateInstance(_type)).Solve;
        }

        private void OnPropertyChanged(String propertyName)
        {
            var handler = _propertyChanged;

            if (handler != null)
            {
                _owner.Dispatcher.BeginInvoke(() => handler(this, new PropertyChangedEventArgs(propertyName)));
            }
        }

        event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
        {
            add { _propertyChanged += value; }
            remove { _propertyChanged -= value; }
        }
    }
}
